插播一下, webpack 5 出來了
gulp 是個老牌的 task runner ,它就只是執行設定好的工作,設定寫起來其實不難,不過現在前端大家主要還是用 bundler ,所以像 gulp 這樣的 task runner 就用的比較少了,事實上還有一個更老的 grunt ,不過這邊不會介紹
gulp 本身只有一個套件:
$ yarn add --dev gulp
不過因為它本身並沒有做什麼事,通常需要搭配 plugin 來使用,等下會用到 del
跟 gulp-babel
,同時這邊也先把 babel 安裝起來:
$ yarn add --dev del gulp-babel @babel/core @babel/preset-env
然後設定 babel ,這邊就不贅述了
gulpfile.js
要 gulp 做什麼都要寫在 gulpfile.js
這個檔案中 (或用 Gulpfile.js
),沒有這個檔案(同時你也沒指定其它代替的檔案), gulp 根本什麼事也都不會做, gulpfile.js
基本格式很簡單:
const { series } = require('gulp')
// 這樣就會有一個 task 叫 cleanup
// 你也可以選擇不 export ,這樣就沒辦法直接用指令呼叫
exports.cleanup = async function cleanup () {}
// 另一個 task 叫 build
exports.build = async function build () {}
// default 是不指定 task 時預設執行的,這邊是依序呼叫 cleanup 跟 build
exports.default = series(cleanup, build)
gulp 的 task 一定是 async 的 (這邊不是指一定要是 async function),所以一定要用某種方式回報自己完成,可以用 callback 也可以用 Promise ,另外還有 stream 可以用
const { src, dest } = require('gulp')
// 用 callback
exports.callback = function (cb) {
cb()
}
// 用 Promise
exports.promise = function () {
return Promise.resolve()
}
// 用 async function
exports.asyncFunc = async function () {}
// 用 async function
exports.asyncFunc = async function () {}
// 用 stream ,這通常是在用 gulp 的 plugin 時
exports.stream = function () {
return src('./src/**/*.js').pipe(dest('lib'))
}
另外其實還有 Observable 跟 EventEmitter 可以用,不過通常 stream 跟 async function (promise) 就很夠用了,有需要可以自己去看
Node.js 中有個用來處理大檔案用的叫 stream 的東西,它會一次只讀入一部份的檔案,用這種方式來處理大檔案,一來是因為 Node.js 可用的 heap 空間一般是有限制的,因此不一定一次能讀入很大的檔案,二來是把檔案一次讀入記憶體,然後在記憶體中一次處理完有點那麼違反了 Node.js 的 event driven 的架構,如果沒有刻意的把主執行緒轉交出去的話,在檔案處理完前其它的工作都會被擱置,而 gulp 則同樣的使用了 Node.js 的 stream ,在裡面傳遞的不是小塊小塊的檔案內容,而是要處理的檔案的資訊與它的內容,這麼說可能不太好懂,下一篇會再來詳細解釋
在上面你應該也有看到,我用到了 gulp 的兩個 API , src
與 dest
:
src
: 建立輸入檔案的 stream ,裡面是放單一個 glob 字串,或是 glob 字串的 arraydest
: 建立輸出的目標,參數一定要是一個資料夾名稱一般使用 gulp 都是使用 src
來指定要處理的檔案有哪些,再用 dest
來指定處理完的檔案要放哪,如果中間都不處理的話,實際上就跟複製檔案沒兩樣:
// 對,就是上面的那個範例,這相當於把 src 下的 js 都照著原本資料夾結構複製到 lib
exports.copy = function () {
return src('./src/**/*.js').pipe(dest('lib'))
}
glob 是一種一般而言是描述要選擇哪些檔案的東西,你可能有聽過「萬用字元」這種稱呼,不過現在的 glob 包含的語法要來的更多,如果你知道怎麼用 glob 或是知道怎麼寫 gitignore (gitignore 就是 glob 的語法) ,你可以跳過這段,如果不清楚的可以看看,畢竟很多東西都支援這樣的語法,像正規表示法一樣
主要的幾個符號:
*
: 0 ~ 多個字元,不包含 /
**
: 同樣是 0 ~ 多個字元,但包含 /
這個分隔資料夾的符號,另外它不能在檔名的一部份 (src/**.js
是不行的),可以用來代表資料夾下包含子資料夾的檔案!
: 只能放在開頭,代表符合這個條件的不要選擇所以底下的幾個範例是:
*.js
: 這層資料夾下副檔名為 js 的檔案src/*.js
: src
下副檔名是 js 的檔案src/**
: src
下包含子資料夾的檔案src/**/*
: src
下包含子資料夾的檔案src/**/*.js
: src
下包含子資料夾的 js 檔!src/**/*.js
: 不要 src
下包含子資料夾的 js 檔不是所有的東西都支援這種語法,有不少其實只支援 *
而已的,不過以 Node.js 而言,有 node-glob
(gulp 底下用的),另一個系列用到,同時也是很常用的 globby
,還有 globby
底下的 fast-glob
,或是單純檢查路徑是不是符合 glob 的 micromatch
跟 minimatch
gulp-babel
終於要來用第一個 plugin 了,不過其實用起來也很簡單
const { src, dest } = require('gulp')
const babel = require('gulp-babel')
exports.build = async function build () {
return src('./src/**/*.js')
// 大部份的 plugin 都是像這樣,夾在 src 跟 dest 中間,就能處理檔案了
.pipe(babel(/* 通常這邊可以放選項 */))
.pipe(dest('lib'))
}
cleanup
task這邊再來用另一個 task ,這次用的是 del
,它能遞迴的刪掉檔案與資料夾,並且會回傳 Promise
const del = require('del')
exports.cleanup = function cleanup () {
// 回傳 Promise 讓 gulp 知道何時跑完
return del('./lib')
}
兩個加起來就有一個能清理上次的檔案與用 babel 重 build 的功能了:
const { src, dest, series } = require('gulp')
const babel = require('gulp-babel')
const del = require('del')
exports.cleanup = function cleanup () {
return del('./lib')
}
exports.build = async function build () {
return src('./src/**/*.js')
.pipe(babel())
.pipe(dest('lib'))
}
exports.default = series(cleanup, build)
另外除了 series
這個照順序執行的,還有 parallel
這個可以同步執行的,兩個也可以組合:
const { series, parallel } = require('gulp')
async function a() {}
async function b() {}
async function c() {}
async function d() {}
exports.default = series(a, parallel(b, c), d)
這樣就是先執行 a ,然後同步執行 b 跟 c ,最後再 d
另外 gulp 還有 watch 的功能,有興趣可以去看看,因為現在其實也不常用了,我就不介紹太多了,下一篇來講 gulp 內部在做什麼